<?php

/**
 * 统一应用入口文件
 * 包含 Console 入口和 Web 入口。
 * @version 1.0.0-beta.220303
 */

namespace myapp;

// 全局配置
$config = [];
// 登录账号配置
$config['auth'] = [
    // 允许登录的帐号列表['登录名'=>['密码']]
    'admins' => [
        'myadmin' => ['password' => 'passWd123'],
        'mydbm' => ['password' => 'passWd123'],
        'admin' => ['password' => '123456'],
    ],
    // 允许访问的 ip 地址列表
    'ips' => ['127.0.0.1', '110.52.27.168'],
    // 指定页面访问验证类型：0=无需验证;1=帐号密码验证;2=IP验证;3=混合验证(先 IP 验证，IP 验证失败则转入帐号密码验证)
    'auth_type' => 1,
    // ===================
    // TOKEN 设置
    // ===================
    // TOKEN_EXP 超时时间 (1 hours, 1 minutes, 30 seconds)
    'token_exp' => '1 hours',    
    // TOKEN 签名密钥，默认值 U6rxInZ7!
    'token_key' => '*f1d33d6',
    // TOKEN 自动延期时长(秒)，每次页面跳转操作会重新生成一个延期过的 TOKEN，类似 session 的效果，只要一直操作就一直不会过期
    'token_referesh_second' => 1800,
];

// 数据库服务器环境配置（支持 MySQL 和 MariaDB 数据库）
$config['environments'] = [
    // 指定缺省默认环境
    'default_database' => 'development',
    // 生产环境
    'production' => [
        'type' => 'mysql',
        'database' => 'db_sample',
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => '',
        'port' => 3306,
        'charset' => 'utf8',
        'prefix' => 't_', // 表名前缀，用于侧边栏过滤表名前缀
        'strict_mode' => true, // 是否严格操作模式，用于在界面上警示提醒。如果是生产环境，建议设值 true。严格操作模式会在界面上对重要操作进行操作确认，避免人为失误造成的生产事故。
        // 外部 .env 配置文件，path 为路径, map 为字段映射，外部配置会覆盖内置的配置内容，startline 为文本配置文件的有效起始行内容
        'envfile' => [
            // 是否使用外部扩展配置文件
            'enable' => true,
            // 外部配置文件的路径，该路径是相对于入口文件的目录位置，而不是相对于本配置文件的目录位置
            'path' => '../.env',
            // 如果设置了 standard 项，则无需设置 startline 和 host 等字段，standard 预设值有 laravel, thinkphp5, thinkphp6。
            'framework' => 'thinkphp6',
        ],
    ],
    // 开发环境
    'development' => [
        // thinkphp 项目的 .env 文件中没有 charset 配置项，因此这里要补充该配置项
        'charset' => 'utf8mb4',
        'envfile' => [
            // 是否使用外部扩展配置文件
            'enable' => true,
            // 外部配置文件的路径，该路径是相对于入口文件的目录位置，而不是相对于本配置文件的目录位置
            'path' => '../.env',
            // 如果设置了 standard 项，则无需设置 startline 和 host 等字段，standard 预设值有 laravel, thinkphp5, thinkphp6。
            'framework' => 'laravel',
        ],
    ],
];

switch (CommonUtils::input('action')) {
    case 'view.phpinfo':
        phpinfo();
        exit;
    case 'post.run-sql':
        $pdoAdapter = PdoAdapter::getPdoAdapter(getDbConfig($config));
        $pdoEx = $pdoAdapter->getConnectException();
        if ($pdoEx) {
            CommonUtils::outputJson(CommonUtils::stdmessage(0, $pdoEx->getMessage()));
        }
        $ver = $pdoAdapter->getDbVersion();
        $sql = CommonUtils::input('sql');
        $sqls = SqlUtils::parseSqlStatement($sql);
        $queryCmds = ['select', 'desc', 'show'];
        $data = [];
        foreach ($sqls as $line) {
            $isQueryCmd = in_array(explode(' ', $line)[0], $queryCmds);
            if ($isQueryCmd) {
                $result = $pdoAdapter->query($line);
            } else {
                $result = $pdoAdapter->execute($sql);
            }
            $data[] = [
                'sql' => $line,
                'isQuery' => intval($isQueryCmd),
                'result' => $result,
            ];
        }
        // =========
        
        $resultHtml = '';
        $toolbarHtml = '';
        $queryCmds = ['select', 'desc', 'show'];
        $cmd = explode(' ', $sql)[0];
        $isQueryCmd = in_array($cmd, $queryCmds);
        if ($isQueryCmd) {
            $url = BaseApp::getStdUrl(['sql' => $sql]);
            $toolbarHtml = <<<html
            <div class="btn-toolbar">
                <div class="btn-group btn-group-sm me-1">
                    <button class="btn btn-secondary" onclick="execFullScreen(this.parentNode.parentNode.nextElementSibling);return false;" title="浏览器窗口全屏">全屏</button>
                    <button class="btn btn-secondary" onclick="printContent('打印结果集', this.parentNode.parentNode.nextElementSibling.innerHTML);return false;" title="打印编辑器内容">打印</button>
                    <a class="btn btn-secondary" href="{$url}" target="_blank" title="在新页面显示结果集">新页面打开</a>
                    <a class="btn btn-secondary" href="{$url}&action=export-query-jsononline" target="_blank" title="生成 JSON 数据，可供 API 接口调用">API-JSON</a>
                </div>
                <div class="btn-group btn-group-sm me-1">
                    <div class="dropdown">
                        <button class="btn btn-sm btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                            导出...
                        </button>
                        <ul class="dropdown-menu">
                            <li>
                                <a class="dropdown-item" href="{$url}&action=export-query-md" target="_blank" title="导出结果集到 Markdown 文件">导出 Markdown</a>
                            </li>
                            <li>
                                <a class="dropdown-item" href="{$url}&action=export-query-excel" target="_blank" title="导出结果集到 Excel 文件">导出 Excel</a>
                            </li>
                            <li>
                                <a class="dropdown-item" href="{$url}&action=export-query-html" target="_blank" title="导出结果集到HTML文件">导出 HTML</a>
                            </li>
                            <li>
                                <a class="dropdown-item" href="{$url}&action=export-query-json" target="_blank" title="导出结果集到 JSON 文件">导出 JSON</a>
                            </li>
                            <li>
                                <a class="dropdown-item" href="{$url}&action=export-query-sql" target="_blank" title="导出结果集到SQL文件">导出 SQL</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
            html;
            $list = BaseApp::getPdoAdapter()->query($sql);
            if (!$list) {
                $resultHtml = '没有查询到任何数据！';
            } else {
                $resultHtml = '<table class="table table-sm table-striped table-bordered table-hover table-click">';
                $resultHtml .= '<thead><tr><th style="min-width:40px;">#<a name="result0" href="###result0" class="lnk-show ps-1" title="显示所有隐藏列">+</a></th>';
                $i = 1;
                foreach ($list as $row) {
                    if ($i == 1) {
                        foreach ($row as $k => $v) {
                            $resultHtml .= '<th>' . $k . '<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>';
                        }
                        $resultHtml .= '</tr></thead><tbody>';
                    }
                    $resultHtml .= '<tr><td>#' . $i . '</td><td>' . implode('</td><td>', array_values($row)) . '</td></tr>';
                    $i++;
                }
                $resultHtml .= '</tbody></table>';
            }
        } else {
            $result = BaseApp::getPdoAdapter()->execute($sql);
            $resultHtml = '执行成功，影响行数：' . var_export($result, true);
        }
        echo <<<html
        {$toolbarHtml}
        <div class="fieldset_content bg-white">
            <div class="bg-light p-2 border my-2">
                <code>{$sql}</code>
            </div>
            <div class="result-container">
            {$resultHtml}
            </div>
        </div>
        html;
        unset($list);
        unset($sql);
        unset($toolbarHtml);
        unset($resultHtml);
        // =========

        CommonUtils::outputJson(CommonUtils::stdmessage(1, '', $data));
        break;
    case 'post.preview-sql':
        $sql = 'data=' . CommonUtils::input('sql');
        CommonUtils::outputJson(CommonUtils::stdmessage(1, 'ok', $sql));
        break;
    case 'post.hostinfo':
        $envs = Config::getEnvs($config['environments']);
        $defaultEnv = '';
        $defaultDb = '';
        if ($envs) {
            $defaultEnv = $config['environments']['default_database'];
            if (!isset($envs[$defaultEnv])) {
                $defaultEnv = current($envs);
            }
            $defaultDb = $envs[$defaultEnv]['database'];
        }
        $serverList = [];
        foreach ($envs as $k => $v) {
            $serverList[] = ['text' => $k, 'value' => $k];
        }
        $dbCfg = getDbConfig($config);
        $data = [
            'clientIP' => $_SERVER['REMOTE_ADDR'],
            'serverIP' => CommonUtils::getHostIP(),
            'connectIP' => $dbCfg['host'] . ':' . $dbCfg['port'],
            'databaseVersion' => PdoAdapter::getPdoAdapter($dbCfg)->getDbVersion(),
            'defaultEnv' => $defaultEnv,
            'defaultDb' => $defaultDb,
            'serverList' => $serverList,
        ];
        CommonUtils::outputJson(CommonUtils::stdmessage(1, 'ok', $data));
        break;
    case 'post.databaselist':
        $pdoAdapter = PdoAdapter::getPdoAdapter(getDbConfig($config));
        $pdoEx = $pdoAdapter->getConnectException();
        if ($pdoEx) {
            CommonUtils::outputJson(CommonUtils::stdmessage(0, $pdoEx->getMessage()));
        }
        $list = $pdoAdapter->getDatabases();
        $databaseList = [];
        foreach ($list as $row) {
            $databaseList[] = ['text' => $row['SCHEMA_NAME'], 'value' => 'SCHEMA_NAME'];
        }
        CommonUtils::outputJson(CommonUtils::stdmessage(1, 'ok', $databaseList));
        break;
    default:
        break;
}

function getDbConfig($config, $defaultEnv = '', $defaultDb = '')
{
    if (!$defaultEnv) {
        $defaultEnv = $config['environments']['default_database'];
    }
    $env = CommonUtils::input('env', $defaultEnv);
    $db = CommonUtils::input('db', $defaultDb);
    $msgInfo = Config::parseEnv($env, $config['environments']);
    if (!$msgInfo) {
        CommonUtils::outputJson($msgInfo);
    }
    $dbCfg = $msgInfo['data'];
    if ($db) {
        $dbCfg['database'] = $db;
    }
    return $dbCfg;
}
$activeConnCfg = Config::parseEnv(CommonUtils::input('env'), $config['environments']);
// ===============================================
//                     类库
// ===============================================

class CommonUtils
{
    public static function input($name, $defaultValue = '')
    {
        if (isset($_REQUEST[$name])) {
            return $_REQUEST[$name];
        }
        $data = [];
        $content = file_get_contents('php://input');
        if ($content) {
            $httpAccept = $_SERVER['HTTP_ACCEPT'];
            if (false !== strpos($httpAccept, 'json')) {
                // Ajax 方式 Post 提交，按照 {key1:val1,key2:val2} 的方式进行编码
                $data = (array) json_decode($content, true);
            } else if (false !== strpos($httpAccept, 'x-www-form-urlencoded')) {
                // 浏览器原生 Post 提交，按照 key1=val1&key2=val2 的方式进行编码
                parse_str($content, $data);
            }
        }
        if (isset($data[$name])) {
            return $data[$name];
        }
        return $defaultValue;
    }
    public static function stdmessage($status, $message, $data = null, $code = 200)
    {
        return ['status' => $status, 'message' => $message, 'data' => $data, 'code' => $code];
    }
    public static function outputJson($data)
    {
        header('Content-type:text/json');
        echo json_encode($data);
        exit;
    }

    /**
     * 返回服务器 IP
     * 参考：SERVER_NAME 和 HTTP_HOST 的区别 (http://blog.sina.com.cn/s/blog_6d96d3160100q39x.html)
     * @version 1.0 2017-10-13 Added.
     */
    public static function getHostIP()
    {
        //return $_SERVER['HTTP_HOST']; // 有服务器域名则优先返回域名，否则返回服务器 ip
        return gethostbyname($_SERVER['SERVER_NAME']); // 返回服务器IP
    }
}
class Config
{

    public static function getEnvs(array $config)
    {
        $envs = [];
        foreach ($config as $key => $data) {
            if ($key == 'default_database') {
                continue;
            }
            $msgInfo = self::parseEnv($key, $config);
            if ($msgInfo['status']) {
                $envs[$key] = $msgInfo['data'];
            }
        }
        return $envs;
    }
    public static function parseEnv(string $envName, array $config)
    {
        if (!$envName) {
            $envName = $config['default_database'];
        }
        if (!isset($config[$envName])) {
            return CommonUtils::stdmessage(0, '错误！环境配置不存在：' . $envName);
        }
        $data = $config[$envName];
        if (isset($data['envfile']['enable']) && $data['envfile']['enable']) {
            $envData = $data['envfile'];
            unset($data['envfile']);
            if (!is_file($envData['path'])) {
                return CommonUtils::stdmessage(0, '错误！env 文件不存在：' . $envData['path']);
            }
            $envLines = file($envData['path']);
            switch ($envData['framework']) {
                case 'thinkphp6':
                    $map = ['TYPE' => 'type', 'DATABASE' => 'database', 'HOSTNAME' => 'host', 'USERNAME' => 'user', 'PASSWORD' => 'password', 'HOSTPORT' => 'port', 'CHARSET' => 'charset',];
                    foreach ($envLines as $line) {
                        if (false !== strpos($line, '[DATABASE]')) {
                            continue;
                        }
                        if (strpos($line, 'CHARSET') === 0) {
                            $kv = explode('=', $line, 2);
                            if (isset($map[$kv[0]]) && isset($kv[1])) {
                                $data[$map[$kv[0]]] = trim($kv[1]);
                            }
                            break;
                        }
                        $kv = explode('=', $line, 2);
                        if (isset($map[$kv[0]]) && isset($kv[1])) {
                            $data[$map[$kv[0]]] = trim($kv[1]);
                        }
                    }
                    break;
                case 'laravel':
                    $map = ['DB_CONNECTION' => 'type', 'DB_DATABASE' => 'database', 'DB_HOST' => 'host', 'DB_USERNAME' => 'user', 'DB_PASSWORD' => 'password', 'DB_PORT' => 'port', 'DB_CHARSET' => 'charset',];
                    foreach ($envLines as $line) {
                        if (strpos($line, 'DB_') === 0) {
                            $kv = explode('=', $line, 2);
                            if (isset($map[$kv[0]]) && isset($kv[1])) {
                                $data[$map[$kv[0]]] = trim($kv[1]);
                            }
                        }
                    }
                    break;
                default:
                    return CommonUtils::stdmessage(0, '错误！env 文件框架格式无效（只支持 laravel、thinkphp6）：' . $envData['framework']);
            }
        }
        if (empty($data['password'])) {
            return CommonUtils::stdmessage(0, '错误！配置内容无效（未配置密码）');
        }
        return CommonUtils::stdmessage(1, '', $data);
    }
}

/**
 * 数据库通用类
 * @version 1.0.1
 * @since 2017-8-23 Added.
 * @since 2017-12-15 增加导出方法和解析服务器信息的方法
 */
class PdoAdapter
{
    /**
     * 数据库连接DSN
     * @var type
     */
    protected $dsn = '';

    /**
     * PDO 数据库连接对象
     * @var \PDO
     */
    protected $link = null;

    /**
     * 数据库环境信息, 数据库名称，数据库帐号等
     * host,database,user,password,port,charset
     * @var array
     */
    protected $config = [
        // 数据库类型，支持  mysql, sqlite 等
        'type'            => '',
        // 服务器地址
        'host'        => '',
        // 数据库名
        'database'        => '',
        // 用户名
        'user'        => '',
        // 密码
        'password'        => '',
        // 端口
        'port'        => '',
        // 连接dsn
        'dsn'             => '',
        // 数据库连接参数
        'params'          => [],
        // 数据库编码默认采用utf8
        'charset'         => 'utf8',
    ];

    /**
     * 数据库连接失败不会主动抛出异常，需要手动获取异常对象
     */
    protected $connectionException = null;

    /**
     * 最后执行的异常信息
     */
    protected $lastException = null;

    /**
     * 当前SQL指令
     * @var string
     */
    protected $queryStr = '';

    /**
     * 事务指令数
     * @var int
     */
    protected $transTimes = 0;

    /**
     * 查询开始时间
     * @var float
     */
    protected $queryStartTime;


    /**
     * 构造方法
     * @param array $dbcfg
     * @throws PDOException
     */
    public function __construct(array $dbcfg)
    {
        if (!isset($dbcfg['password'])) {
            return;
        }
        $this->config = $dbcfg;
        $this->connect($dbcfg);
        if (!is_null($this->connectionException) && false !== strpos($this->connectionException->getMessage(), 'Unknown database')) {
            // 如果是数据库未创建而出错，则尝试去掉数据库参数再连接一次
            unset($dbcfg['database']);
            $this->connect($dbcfg);
        }
    }

    private static $pdoAdapter = null;
    /**
     * 获取PDO实例对象
     */
    public static function getPdoAdapter($dbCfg)
    {
        if (self::$pdoAdapter != null) {
            return self::$pdoAdapter;
        }
        self::$pdoAdapter = new PdoAdapter($dbCfg);
        return self::$pdoAdapter;
    }
    /**
     * 连接数据库
     */
    public function connect($dbCfg)
    {
        $this->connectionException = null;
        $this->dsn = $this->getDSN($dbCfg);
        if (empty($this->dsn)) {
            $this->connectionException = new \PDOException('数据库配置文件无效!' . $this->dsn);
            return;
        }
        try {
            $this->link = new \PDO($this->dsn, $dbCfg['user'], $dbCfg['password']);
            $this->link->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); // 提高错误级别
        } catch (\PDOException $ex) {
            // 命令行环境下错误消息显示不出来的解决方法：
            // 设置CMD环境编码为GBK:936,utf-8:65001，否则PDO实例化异常消息会乱码
            // exec("CHCP 936");
            // 浏览器环境下错误消息显示不出来的解决方法：
            // header("Content-Type:text/html;charset=utf-8");
            // 通用解决方法(直接转码)：
            // echo iconv('gbk', 'utf-8', $ex->getMessage());
            // $message = '数据库连接失败：请检查数据库是否可用或数据库连接信息是否正确 [' . $this->dsn . ']';
            $message = '数据库连接失败：' . iconv('gbk', 'utf-8', $ex->getMessage());
            $this->connectionException = new \PDOException($message);
        }
    }
    public function __destruct()
    {
        $this->dispose();
    }
    public function dispose()
    {
        unset($this->config);
        unset($this->link);
        unset($this->queryOptions);
        unset($this->connectionException);
        unset($this->lastException);
    }
    public function getDSN(array $server)
    {
        // 解析host和port是否有端口转发
        $host = $server['host'];
        $port = $server['port'];
        // 数据库类型
        $adapter = 'mysql';
        if (isset($server['type'])) {
            $adapter = trim($server['type']);
        }
        switch ($adapter) {
            case 'mysql':
                $dsnarr = ['host' => $host, 'port' => $port];
                if (isset($server['charset'])) {
                    $dsnarr['charset'] = $server['charset'];
                }
                if (!empty($server['database'])) {
                    $dsnarr['dbname'] = $server['database'];
                }
                $dsn = '';
                foreach ($dsnarr as $name => $value) {
                    $dsn .= $name . '=' . $value . ';';
                }
                $dsn = 'mysql:' . $dsn;
                break;
            default:
                $dsn = '';
                break;
        }
        return $dsn;
    }
    /**
     * 数据库连接失败不会主动抛出异常，需要手动获取异常对象
     */
    public function getConnectException()
    {
        return $this->connectionException;
    }
    /**
     * 数据库操作失败不会主动抛出异常，需要手动获取异常对象
     */
    public function getLastException()
    {
        return $this->lastException;
    }
    /**
     * 返回数据库服务器环境变量的值
     * host,database,user,password,port,charset
     * @param string $name 名称
     * @param mixed $defv 默认值
     */
    public function getConfigValue($name, $defv = '')
    {
        if (!$name) {
            return $this->config;
        }
        return isset($this->config[$name]) ? $this->config[$name] : $defv;
    }

    /**
     * 返回数据库名称，判断数据库名称是否是当前数据库,不一致则切换到该数据库
     * @param string $db_name 参数指定的数据库名称
     * @return string 数据库名称
     */
    public function checkDbName($db_name)
    {
        $cfgDbName = isset($this->config['database']) ? $this->config['database'] : '';
        if (!$db_name && $cfgDbName) {
            $db_name = $cfgDbName;
        }
        if (!$db_name) {
            return '';
        }
        // 动态切换数据库
        if ($db_name != $cfgDbName) {
            $this->lastException = null;
            try {
                $this->execute('use ' . $db_name);
            } catch (\PDOException $ex) {
                $this->lastException = $ex;
                exit('checkDbName()方法执行错误：' . $ex->getMessage());
            }
        }
        return $db_name;
    }
    /**
     * 选择数据库
     */
    public function selectDb($db_name)
    {
        if (!$db_name) {
            return;
        }
        $this->execute('use ' . $db_name);
    }
    // ===================================== 数据库事务 ========================================

    /**
     * 执行数据库事务
     * @access public
     * @param  callable $callback 数据操作方法回调
     * @return mixed
     * @throws PDOException
     * @throws \PDOException
     * @throws \Throwable
     */
    public function transaction(callable $callback)
    {
        $this->startTrans();
        try {
            $result = null;
            if (is_callable($callback)) {
                $result = $callback($this);
            }
            $this->commit();
            return $result;
        } catch (\PDOException | \Throwable $ex) {
            $this->rollback();
            throw $ex;
        }
    }

    /**
     * 启动事务
     * @access public
     * @return void
     * @throws \PDOException
     * @throws \PDOException
     */
    public function startTrans(): void
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return;
        }
        ++$this->transTimes;
        if (1 == $this->transTimes) {
            if (false === $this->link->beginTransaction()) {
                exit('开启事务失败！');
            }
        }
    }

    /**
     * 用于非自动提交状态下面的查询提交
     * @access public
     * @return void
     * @throws \PDOException
     */
    public function commit(): void
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return;
        }
        if (1 == $this->transTimes) {
            if (false === $this->link->commit()) {
                exit('提交事务失败！');
            }
        }
        --$this->transTimes;
        echo '提交事务嵌套', $this->transTimes, PHP_EOL;
    }

    /**
     * 事务回滚
     * @access public
     * @return void
     * @throws \PDOException
     */
    public function rollback(): void
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return;
        }
        if (1 == $this->transTimes) {
            if (false === $this->link->rollBack()) {
                exit('回滚事务失败！');
            }
        }
        $this->transTimes = max(0, $this->transTimes - 1);
    }

    /**
     * 执行一条预处理语句
     * 如果执行失败则返回 false，执行成功则返回被影响的行数
     * 注意：这里不能用foreach，参考bindParam陷阱(值参数是引用型变量)：http://www.laruence.com/2012/10/16/2831.html
     * @param string $sqlStr SQL 命令
     * @param array $params 参数值
     */
    public function execute($sql, $params = [])
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return false;
        }
        $this->lastException = null;
        $stmt = $this->link->prepare($sql);
        try {
            if ($params) {
                $stmt->execute($params);
            } else {
                $stmt->execute();
            }
            return $stmt->rowCount();
        } catch (\PDOException $ex) {
            $this->lastException = $ex;
            return false;
        }
    }
    /**
     * 执行一条SQL语句
     * 如果执行失败则返回 false，执行成功则返回被影响的行数
     * @param string $sqlStr
     */
    public function exec($sql)
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return false;
        }
        $this->lastException = null;
        try {
            return $this->link->exec($sql);
        } catch (\PDOException $ex) {
            $this->lastException = $ex;
            return false;
        }
    }

    /**
     * 执行SQL，返回一行记录
     * 执行成功则返回一行记录，执行失败则返回 null
     * 注意：这里不能用foreach，参考bindParam陷阱(值参数是引用型变量)：http://www.laruence.com/2012/10/16/2831.html
     * @param string $sqlStr SQL 命令
     * @param array $params 参数值
     */
    public function find($sql, $params = [])
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return null;
        }
        $this->lastException = null;
        $stmt = $this->link->prepare($sql);
        try {
            if ($params) {
                $stmt->execute($params);
            } else {
                $stmt->execute();
            }
            return $stmt->fetch(\PDO::FETCH_ASSOC);
        } catch (\PDOException $ex) {
            $this->lastException = $ex;
            return null;
        }
    }
    /**
     * 执行SQL，返回字段值
     * 执行成功则返回一个值，执行失败则返回 null
     */
    public function value($sql, $params = [])
    {
        $data = $this->find($sql, $params);
        return is_array($data) ? current($data) : null;
    }

    /**
     * 执行SQL 命令并返回结果集数组
     * 执行成功则返回数组，执行失败则返回空数组
     * 注意：这里不能用foreach，参考bindParam陷阱(值参数是引用型变量)：http://www.laruence.com/2012/10/16/2831.html
     * @param string $sqlStr SQL 命令
     * @param array $params 参数值
     */
    public function query($sql, $params = [], $fetch_style = \PDO::FETCH_ASSOC)
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return [];
        }
        $this->lastException = null;
        $stmt = $this->link->prepare($sql);
        try {
            if ($params) {
                $stmt->execute($params);
            } else {
                $stmt->execute();
            }
            return $stmt->fetchAll($fetch_style);
        } catch (\PDOException $ex) {
            $this->lastException = $ex;
            return [];
        }
    }

    /**
     * 批处理执行SQL语句
     * 批处理的指令都认为是execute操作
     * @param array $sqls SQL批处理指令
     * @param boolean $isexecute 指示方法是执行或查询
     * @param boolean $isrollback 遇到错误是否回滚
     * @return array 错误消息数组
     * @version 1.0 2016-6-15 xcaller Added.
     * @example batchQuery($asql);
     */
    public function batchQuery($sqls = [], $is_execute = false, $is_rollback = false)
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return null;
        }
        $this->lastException = null;
        // 错误消息数组
        $outData = ['status' => 0, 'affected_rows' => 0, 'data' => ['没有任何命令']];
        if (!$sqls) {
            return $outData;
        }
        // 如果参数是单条SQL 命令，则自动封装为数组
        if (!is_array($sqls)) {
            $sqls = array($sqls);
        }
        $outData['data'] = []; // 清空错误消息数组
        $effectRows = 0; // 受影响的行数
        // 自动启动事务支持，提高执行性能        
        $this->link->beginTransaction(); // mysql_query("BEGIN");
        try {
            foreach ($sqls as $sql) {
                if (!trim($sql)) {
                    continue;
                }
                if ($is_execute) {
                    $result = $this->link->exec($sql);
                    $effectRows += $result;
                } else {
                    $result = $this->link->query($sql);
                }
                // 错误处理
                if (false === $result) {
                    // [SQLSTATE error code, Driver-specific error code, Driver-specific error message] | mysql_error();
                    $errorInfo = $this->link->errorInfo();
                    $result['data'][] = $errorInfo[2];
                    // 发生错误自动回滚事务
                    if ($is_rollback) {
                        $this->link->rollBack(); // mysql_query("ROLLBACK");
                    }
                }
            }
            // 提交事务
            $this->link->commit(); // mysql_query("COMMIT"); mysql_query("END");
            $outData['status'] = 1;
            $outData['affected_rows'] = $effectRows;
            return $outData;
        } catch (\PDOException $ex) {
            // 回滚事务
            $this->link->rollBack(); // mysql_query("ROLLBACK"); mysql_query("END");
            $this->lastException = $ex;
        }
    }
    /**
     * 返回数据库版本号
     * @return string 数据库版本号
     */
    public function getDbVersion()
    {
        $sql = 'select version() as `version`;';
        $versionInfo = $this->value($sql);
        return $versionInfo ? $versionInfo : '';
    }

    /**
     * 返回数据库服务器状态
     */
    public function getDbStatus()
    {

        $sql = 'show status;';
        return $this->query($sql);
    }
    /**
     * 返回数据库配置变量列表
     * @return array 配置变量列表
     */
    public function getDbVariables()
    {
        $sql = 'show global variables;';
        return $this->query($sql);
    }

    /**
     * 返回所有数据库
     * @param string $db_name 数据库名称
     * @return array SCHEMA_NAME,DEFAULT_CHARACTER_SET_NAME,DEFAULT_COLLATION_NAME
     */
    public function getDatabases($db_name = '', $with_key_name = false)
    {
        $sqlStr = 'select SCHEMA_NAME,DEFAULT_CHARACTER_SET_NAME,DEFAULT_COLLATION_NAME from '
            . 'information_schema.schemata where SCHEMA_NAME';
        if ($db_name) {
            $sqlStr .= ' = ?';
            $params = [$db_name];
        } else {
            $sqlStr .= ' not in (\'mysql\',\'information_schema\',\'performance_schema\');';
            $params = [];
        }
        $sqlStr .= ' order by SCHEMA_NAME';
        $list = $this->query($sqlStr, $params);
        if ($with_key_name && $list) {
            $list = $this->setArrayKeyByName($list, 'SCHEMA_NAME');
        }
        return $list;
    }

    /**
     * 返回数据表元数据
     * @param string $db_name
     * @return array SCHEMA_NAME,DEFAULT_CHARACTER_SET_NAME,DEFAULT_COLLATION_NAME,DATA_LENGTH,TABLE_COUNT,TABLE_ROWS
     */
    public function getDatabaseInfo($db_name = '')
    {
        $db_name = $this->checkDbName($db_name);
        $list = $this->getDatabases($db_name);
        if (is_array($list)) {
            $data = current($list);
            $dbInfo = $this->getDatabaseSize($db_name);
            if ($dbInfo) {
                $data = array_merge($data, $dbInfo);
            }
            return $data;
        }
        return null;
    }
    /**
     * 返回数据库占用空间大小
     */
    public function getDatabaseSize($db_name)
    {
        $sqlStr = "select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as DATA_LENGTH, sum(TABLE_ROWS) as TABLE_ROWS, count(*) as TABLE_COUNT from information_schema.TABLES where table_schema=:database;";
        $list = $this->query($sqlStr, ['database' => $db_name]);
        return $list ? current($list) : [];
    }
    /**
     * 返回所有数据表名称
     */
    public function getTableNames($db_name = '')
    {
        $tables = $this->getTables($db_name, '', true);
        return is_array($tables) ? array_keys($tables) : [];
    }

    /**
     * 返回所有数据表
     * @param string $db_name
     * @param string $table_name
     * @return array TABLE_NAME, TABLE_TYPE, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT, CHARACTER_MAXIMUM_LENGTH
     */
    public function getTables($db_name = '', $tableNames = '', $with_key_name = false, $field = '')
    {
        $db_name = $this->checkDbName($db_name);
        if (!$field) {
            $field = 'TABLE_NAME, TABLE_TYPE, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT';
        }
        $sqlStr = "select {$field} from information_schema.TABLES where TABLE_SCHEMA=:database AND TABLE_TYPE='BASE TABLE' order by TABLE_NAME";
        $params = ['database' => $db_name];

        $list = $this->query($sqlStr, $params);
        if ($list) {
            $nameField = 'TABLE_NAME';
            $tableNames = is_array($tableNames) ? $tableNames : array_filter(explode(',', $tableNames));
            if ($tableNames) {
                $list2 = [];
                foreach ($list as $row) {
                    if (in_array($row[$nameField], $tableNames)) {
                        $list2[] = $row;
                    }
                }
                $list = $list2;
                unset($list2);
            }
            if ($with_key_name) {
                $list = $this->setArrayKeyByName($list, $nameField);
            }
        }
        return $list;
    }

    /**
     * 返回数据表元数据
     * @param string $db_name
     * @param string $table_name
     * @param string $field
     * @return array TABLE_NAME, TABLE_TYPE, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT
     */
    public function getTableInfo($db_name, $table_name, $field = '')
    {
        $db_name = $this->checkDbName($db_name);
        $list = $this->getTables($db_name, $table_name);
        if (is_array($list)) {
            $data = current($list);
            $data['TABLE_COLUMNS'] = $this->getColumnCount($db_name, $table_name);
            if ($field && isset($data[$field])) {
                return $data[$field];
            }
            return $data;
        }
        return null;
    }
    /**
     * 返回数据表记录数量少于指定数量的表集合
     * @param string $db_name
     * @param int $eltcount
     */
    public function getEmptyTables($db_name, $eltcount = 0, $prefix = '')
    {
        $db_name = $this->checkDbName($db_name);
        $list = $this->query('show tables;');
        $tables = [];
        $tableName = '';
        foreach ($list as $row) {
            $tableName = current($row);
            if ($prefix && false === strpos($tableName, $prefix)) {
                continue;
            }
            $count = $this->count($tableName);
            if ($count > $eltcount) {
                continue;
            }
            $tables[$tableName] = $row;
        }
        return $tables;
    }
    /**
     * 判断数据表是否存在
     */
    public function hasTable($table_name, $db_name = '')
    {
        if ($this->link == null) {
            $this->lastException = new \PDOException('数据库未连接');
            return null;
        }
        $db_name = $this->checkDbName($db_name);
        $sqlStr = 'select count(*) from information_schema.tables where TABLE_SCHEMA=:database AND TABLE_NAME=:table;';
        $stmt = $this->link->prepare($sqlStr);
        $stmt->execute(['database' => $db_name, 'table' => $table_name]);
        return current($stmt->fetch(\PDO::FETCH_ASSOC)) > 0;
    }

    /**
     * 返回所有索引
     * @param string $db_name
     * @param string $table_name
     * @return array Table, Non_unique, Key_name, Seq_in_index, Column_name, Collation, Cardinality, Sub_part, Packed,
     *          Null, Index_type
     */
    public function getIndexes($db_name, $table_name)
    {
        $db_name = $this->checkDbName($db_name);
        // Key_name, Column_name, Index_type, Index_comment, Collation, Cardinality, Non_unique
        $list = $this->query('show index from ' . filter_var($db_name) . '.' . filter_var($table_name));
        return $list;
    }

    /**
     * 返回所有列
     * @param string $db_name
     * @param string $table_name
     * @param boolean $with_key_name
     * @return array COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_SET_NAME, COLLATION_NAME, `PRIVILEGES`, 
     *      EXTRA, COLUMN_COMMENT, ORDINAL_POSITION, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, COLUMN_KEY
     */
    public function getColumns($db_name, $table_name, $with_key_name = false)
    {
        $db_name = $this->checkDbName($db_name);
        $field = 'COLUMN_NAME, COLUMN_TYPE, DATA_TYPE, COLUMN_DEFAULT, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_KEY, EXTRA, COLUMN_COMMENT, `PRIVILEGES`';
        $sqlStr = 'select ' . $field . ' from information_schema.columns WHERE TABLE_SCHEMA=:database AND TABLE_NAME=:table order by ORDINAL_POSITION';
        $list = $this->query($sqlStr, ['database' => $db_name, 'table' => $table_name]);
        if ($with_key_name && $list) {
            $list = $this->setArrayKeyByName($list, 'COLUMN_NAME');
        }
        return $list;
    }
    /**
     * 设置数组的键名
     */
    private function setArrayKeyByName(array $list, string $key_field)
    {
        $data = [];
        foreach ($list as $row) {
            $data[$row[$key_field]] = $row;
        }
        return $data;
    }
    /**
     * 返回数据表的列数量
     */
    public function getColumnCount($db_name, $table_name)
    {
        $db_name = $this->checkDbName($db_name);
        $sqlStr = 'select count(*) from information_schema.COLUMNS where TABLE_SCHEMA=:database and table_name =:table';
        $value = $this->value($sqlStr, ['database' => $db_name, 'table' => $table_name]);
        return $value ? $value : 0;
    }
    /**
     * 返回创建数据表的命令
     */
    public function getCreateTableDDL($db_name, $table_name)
    {
        $db_name = $this->checkDbName($db_name);
        $sqlStr = "show create table {$db_name}.{$table_name}";
        $data = $this->find($sqlStr);
        return isset($data['Create Table']) ? $data['Create Table'] : '';
    }
    /**
     * 返回所有视图
     * @param string $db_name
     * @return array TABLE_NAME
     */
    public function getViews($db_name = '', $with_key_name = false)
    {
        $db_name = $this->checkDbName($db_name);
        $sqlStr = 'select TABLE_NAME, VIEW_DEFINITION, DEFINER, CHARACTER_SET_CLIENT, COLLATION_CONNECTION from information_schema.views where TABLE_SCHEMA=:database order by TABLE_NAME;';
        $list = $this->query($sqlStr, ['database' => $db_name]);
        if ($with_key_name && $list) {
            $list = $this->setArrayKeyByName($list, 'TABLE_NAME');
        }
        return $list;
    }
    /**
     * 返回所有数据库函数
     */
    public function getFunctions($db_name = '', $with_key_name = false)
    {
        return $this->getRoutines($db_name, 'FUNCTION', $with_key_name);
    }
    /**
     * 返回所有数据库存储过程
     */
    public function getProcedures($db_name = '', $with_key_name = false)
    {
        return $this->getRoutines($db_name, 'PROCEDURE', $with_key_name);
    }
    /**
     * 返回所有程序
     * @param string $db_name
     * @param string $type FUNCTION | PROCEDURE
     * @return array ROUTINE_NAME, ROUTINE_DEFINITION, DEFINER, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, 
     *      DATA_TYPE, CREATED, LAST_ALTERED, ROUTINE_COMMENT, ROUTINE_TYPE, CHARACTER_MAXIMUM_LENGTH, 
     *      CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, DATETIME_PRECISION
     */
    public function getRoutines($db_name = '', $type = '', $with_key_name = false)
    {
        $db_name = $this->checkDbName($db_name);
        if ($type) {
            $type = strtoupper($type);
        }
        $field = 'ROUTINE_NAME, ROUTINE_DEFINITION, DEFINER, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, DATA_TYPE, CREATED, LAST_ALTERED, ROUTINE_COMMENT';
        $sqlStr = 'select ' . $field . ' from information_schema.routines WHERE ROUTINE_SCHEMA=:database'
            . ($type ? " and ROUTINE_TYPE='$type'" : '') . ' order by ROUTINE_TYPE, ROUTINE_NAME;';
        $sqlStr = 'select ' . $field . ' from information_schema.routines limit 10';
        $list = $this->query($sqlStr, ['database' => $db_name]);
        if ($with_key_name && $list) {
            $list = $this->setArrayKeyByName($list, 'ROUTINE_NAME');
        }
        return $list;
    }

    /**
     * 返回程序的所有参数
     * <br />ORDINAL_POSITION=0为返回参数
     * @param string $db_name
     * @param string $routine
     * @return array ORDINAL_POSITION, PARAMETER_MODE, PARAMETER_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH,
     *          DTD_IDENTIFIER
     */
    public function getRoutineParams($db_name, $routine)
    {
        $db_name = $this->checkDbName($db_name);
        $sqlStr = 'select ORDINAL_POSITION, PARAMETER_MODE, PARAMETER_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, '
            . 'DTD_IDENTIFIER, ROUTINE_TYPE from information_schema.PARAMETERS where SPECIFIC_SCHEMA=:database and '
            . 'SPECIFIC_NAME=:routine';
        $list = $this->query($sqlStr, ['database' => $db_name, 'routine' => $routine]);
        return $list;
    }
    /**
     * 返回程序的所有参数定义
     * @param string $db_name
     * @param string $routine
     * @return array
     */
    public function getRoutineParamsSimple($db_name, $routine)
    {
        $db_name = $this->checkDbName($db_name);
        $list = $this->getRoutineParams($db_name, $routine);
        $outlist = [];
        foreach ($list as $row) {
            $objname = $row['PARAMETER_NAME'];
            $datatype = $row['DATA_TYPE'];

            if ($objname) {
                $outlist[] = $row['PARAMETER_MODE'] . ' ' . $objname . ' '
                    . ($datatype == 'varchar' ? $row['DTD_IDENTIFIER'] : $datatype);
            } else {
                $outlist[] = 'returns: ' . ($datatype == 'varchar' ? $row['DTD_IDENTIFIER'] : $datatype);
            }
        }
        return $outlist;
    }

    /**
     * 获取数据库全文查询的命令
     */
    public function getFullTextSearchSqls($word, $table_name)
    {
        if (!$word || !$table_name) {
            return [];
        }
        $allowTypes = ['tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double', 'decimal', 'char', 'varchar'];
        $charTypes = ['char', 'varchar'];
        $isNumType = is_numeric($word);
        $tables = [];
        if ($table_name == 'all') {
            $tableInfos = $this->getTables('');
            foreach ($tableInfos as $row) {
                $tables[] = $row['TABLE_NAME'];
            }
        } else {
            $tables[] = $table_name;
        }
        $where = [];
        $sqls = [];
        foreach ($tables as $table_name) {
            $where = [];
            $columns = $this->getColumns('', $table_name);
            foreach ($columns as $column) {
                $dataType = $column['DATA_TYPE']; // 小写
                if (!in_array($dataType, $allowTypes)) {
                    continue;
                }
                // 关键词是数字，则所有字段类型都查询；关键词是字符串，则只查询字符串类型的字段
                if (!$isNumType && !in_array($dataType, $charTypes)) {
                    continue;
                }
                $where[] = "`{$column['COLUMN_NAME']}` like '%{$word}%'";
            }
            if ($where) {
                $sqls[] = "select * from {$table_name} where " . implode(' or ', $where);
            }
        }
        return $sqls;
    }
}

/**
 * SQL 命令工具类
 */
class SqlUtils
{
    /**
     * 格式化sql脚本
     * <br />去除表注释、字段注释、多余空格、drop命令
     * @param string $str
     * @param string $break_line
     * @return string
     */
    public static function formatSql($str, $break_line = '\r')
    {
        $str = preg_replace("/ comment '[^']*'/", '', $str); // comment '...'
        $str = preg_replace("/ comment='[^']*'/", '', $str); // comment='...'
        $str = preg_replace("/--[^$break_line]+/", '', $str); // -- ...
        $str = preg_replace("/\/\*[^\/]+\//", '', $str); // /*...*/
        $str = preg_replace("/drop[^;]+;/", '', $str); // drop table ...;
        $str = preg_replace('/[\s]+/', ' ', trim($str)); // 多个空格合并成一个
        return $str;
    }

    /**
     * 去除多行SQL中的所有注释内容
     * 加载 sql 文件为分号分割的数组，注意最后一条命令语句要以分号结尾，否则最后一条命令不会解析出来
     * 支持存储过程和函数提取，自动过滤注释
     */
    public static function parseSqlStatement($lines, $routinesplitor = ';;')
    {
        if (is_string($lines)) {
            if (is_file($lines)) {
                $lines = file($lines, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); // 读取文件到数组
            } else {
                $lines = explode("\n", $lines);
            }
        }
        $outlines = [];
        $lines = array_filter($lines);
        if (!$lines) {
            return $outlines;
        }
        $stmt = '';
        $isroutine = false;
        $routinesplitorend = $routinesplitor[0];
        $isskip = false; // 是否过滤批量注释
        foreach ($lines as $line) {
            $line = trim($line); // 过滤头尾空格
            if (!$line) {
                continue; // 过滤空行
            }
            if (0 === strpos($line, '/*')) { // 批量注释开始
                if (false === strpos($line, '*/')) { // 如果不是单行注释，则从此行开始，一直过滤到发现*/字符为止
                    $isskip = true; // 过滤批量注释
                }
                continue; // 该批量注释是单行注释
            }
            if ($isskip && (false !== strpos($line, '*/'))) { // 批量注释结束
                $isskip = false;
                continue;
            }
            if ($isskip) {
                continue; // 过滤批量注释
            }
            if (0 === strpos($line, '-- ')) {
                continue; // 过滤单行注释
            }
            $lowerline = strtolower($line);
            // 过滤注释 ...
            // 提取存储过程和函数
            if (!$isroutine && 0 === strpos($lowerline, 'delimiter ' . $routinesplitor)) {
                $isroutine = true;
                continue;
            }
            if ($isroutine) {
                // drop 语句独立一行
                if (0 === strpos($lowerline, 'drop function ') || 0 === strpos($lowerline, 'drop procedure ')) {
                    $outlines[] = $line;
                    $stmt = '';
                    continue;
                }
                if (0 === strpos($lowerline, 'delimiter ' . $routinesplitorend)) {
                    $isroutine = false;
                    $outlines[] = $stmt;
                    $stmt = '';
                    continue;
                }
                $stmt .= $line . ' ';
                continue;
            }
            // 提取普通语句，支持一个语句拆分成很多行的写法，注意语句结束后必须以分号结尾
            $stmt .= $line . ' '; // 串联行内容要加个空格，防止 select a\rfrom table，拼凑成 select afrom table
            if (false !== strpos($line, ';') && strlen($line) == (strpos($line, ';') + 1)) {
                $outlines[] = $stmt;
                $stmt = '';
            }
        }
        // 兼容单sql语句，且没有以分号结尾的情况
        // if (!$outlines && $stmt) { // 这样写最后一条命令如果没有分号结尾会解析不出来
        if ($stmt) {
            $outlines[] = $stmt;
        }
        return $outlines;
    }

    /**
     * 分割多行SQL文本到数组
     * @param string $sqltext 多行SQL文本
     * @return array 返回数组
     */
    public static function splitSqlText($sqltext)
    {
        return empty($sqltext) ? [] : array_filter(explode(";\r", $sqltext));
    }

    /**
     * 解析 SQL 语句中的首个对象名称
     * @param string $sqlstmt
     * @return array [object, type]
     * @version 2017-12-20
     */
    public static function getObjectBySql($sqlstmt)
    {
        $return = array('object' => '', 'type' => '');
        if (!$sqlstmt) {
            return $return;
        }
        $sqlstmt = trim($sqlstmt);
        if (false === strpos($sqlstmt, ' ')) {
            return $return;
        }
        $count = 0;
        $firstword = substr($sqlstmt, 0, strpos($sqlstmt, ' '));
        switch ($firstword) {
            case 'select': // select ... from ($table|$view) | select $func
                $count = preg_match('/ from[\s]+([^\s|^\(]+)/', $sqlstmt, $matches);
                if (!$count && false === strpos($sqlstmt, ' from ')) {
                    $return['type'] = 'function';
                    $count = preg_match('/select[\s]+([^\s|^\(]+)/', $sqlstmt, $matches);
                }
                break;
            case 'delete': // delete from $table
                $count = preg_match('/ from[\s]+([^\s]+)/', $sqlstmt, $matches);
                break;
            case 'insert': // insert into $table
                $count = preg_match('/ into[\s]+([^\s]+)/', $sqlstmt, $matches);
                break;
            case 'update': // update $table
                $count = preg_match('/update[\s]+([^\s]+)/', $sqlstmt, $matches);
                break;
            case 'alter': // alter table $table
                $count = preg_match('/ table[\s]+([^\s]+)/', $sqlstmt, $matches);
                break;
            case 'call': // call $proc
                $count = preg_match('/call[\s]+([^\s|^\(]+)/', $sqlstmt, $matches);
                $return['type'] = 'procedure';
                break;
        }
        if ($count) {
            $return['object'] = $matches[1];
            if (!$return['type']) {
                $return['type'] = 'table';
            }
        }
        return $return;
    }
    /**
     * 获取示例语句
     */
    public static function getDefaultExampleSql($action, $isComment = true)
    {
        $sqlstmt = '';
        $egsqlarr = [];
        switch ($action) {
            case 'eg.create_database':
                $egsqlarr = [
                    'CREATE DATABASE IF NOT EXISTS db_test DEFAULT CHARSET utf8 COLLATE utf8_general_ci;',
                    'CREATE DATABASE IF NOT EXISTS db_test DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;',
                    '-- ----',
                    '-- utf8 和 utf8mb4 区别：',
                    '-- 1. utf8 一个字符占用 3 个字节，utf8mb4 一个字符占用 4 个字节，但实际应用中，数据库体积差距可以忽略不计',
                    '-- 2. utf8 不支持 emoji 表情，utf8mb4 支持 emoji 表情',
                    '-- 3. 在排序和校对规则方面，utf8_unicode_ci 比较准确，utf8_general_ci 速度比较快',
                    '-- 4. 如果数据表使用 utf8mb4 编码，数据库也要使用 utf8mb4 编码',
                    '-- 5. 字段的最大长度是 767 字节，utf8 编码可以支持 255 个字符（255*3=765），utf8mb4 只能支持 191 个'
                        . '字符（191*4bit=764）',
                    '-- 6. 建议普通表使用 utf8，如果这个表需要支持 emoji 表情符号就使用 utf8mb4',
                    '-- 7. 包含 emoji 表情符号的字段需设置为编码为 utf8mb4_unicode_ci，字段最长为 191 个字符，否则会报错：'
                        . 'Specified key was too long; max key length is 767 bytes',
                    "-- -- 例如 `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';"
                ];
                break;
            case 'eg.create_table':
                $egsqlarr = array(
                    "drop table if exists `t_test`;",
                    "create table if not exists `t_test` (",
                    "   id int auto_increment primary key comment '主键编号',",
                    "   `name` varchar(32) not null default '' comment '名称',",
                    "   `content` varchar(20480) not null default '' comment '内容',",
                    "   user_id int not null default 0 comment '用户编号',",
                    "   ordinal smallint not null default 0 comment '排列次序',",
                    "   `status` smallint not null default 0 comment '状态:0=未审核,1=正常,4=关闭',",
                    "   created_time timestamp not null default CURRENT_TIMESTAMP comment '创建时间',",
                    "   PRIMARY KEY(id),",
                    "   UNIQUE  (user_id, `name`),",
                    "   FOREIGN KEY (category_id) REFERENCES t_category(id)",
                    "   CONSTRAINT `fk_test_category_id` FOREIGN KEY (`category_id`) REFERENCES `t_category` (`id`),",
                    "   INDEX idx_userid (user_id)",
                    ") comment 'test表[v1.0 " . date('Y-m-d') . " by author]' ENGINE=InnoDB default CHARSET=utf8;",
                );
                break;
            case 'eg.create_view':
                $egsqlarr = array(
                    "drop view if exists `v_test`;",
                    "create or replace view v_test as select t1.*,t2.colname from t_test1 t1 left join t_test2 t2 "
                        . "on t1.id=t2.fkid order by t1.id desc;",
                );
                break;
            case 'eg.create_function':
                $egsqlarr = array(
                    "DELIMITER ;;",
                    "drop function if exists fn_indexOf;;",
                    "create function fn_indexOf",
                    "(",
                    "    -- 要分割的字符串",
                    "    pstr_text varchar(20480),",
                    "    -- 分隔符号",
                    "    pstr_splitor varchar(10),",
                    "    -- 取第几个元素",
                    "    pint_index int",
                    ")",
                    "returns varchar(1024)",
                    "begin",
                    "    declare vint_location int;",
                    "    declare vint_start int;",
                    "    declare vint_next int;",
                    "    declare vint_seed int;",
                    "    set pstr_text=ltrim(rtrim(pstr_text));",
                    "    set vint_start=1;",
                    "    set vint_next=1;",
                    "    set vint_seed=length(pstr_splitor);",
                    "    set vint_location=position(pstr_splitor in pstr_text);",
                    "    while vint_location<>0 and pint_index>vint_next do",
                    "        set vint_start=vint_location+vint_seed;",
                    "        set vint_location=locate(pstr_splitor,pstr_text,vint_start);",
                    "        set vint_next=vint_next+1;",
                    "    end while;",
                    "    if vint_location =0 THEN",
                    "        set vint_location =length(pstr_text)+1;",
                    "    end if;",
                    "    -- 这里存在两种情况：1、字符串不存在分隔符号 2、字符串中存在分隔符号，跳出 while 循环后，"
                        . "vint_location为0，那默认为字符串后边有一个分隔符号。",
                    "    set pstr_text=substring(pstr_text,vint_start,vint_location-vint_start);",
                    "    return pstr_text;",
                    "end;;",
                    "DELIMITER ;",
                );
                $sqlstmt = "/**\r获取数组中指定位置的元素\r如果超过界限则返回最后界限的值\r@version 1.0 2014-11-12\r"
                    . "调用示例：select func_indexOf('8,9,4',',',2)\r返回值：9\r*/\r";
                break;
            case 'eg.create_procedure':
                $egsqlarr = array(
                    "DELIMITER ;;",
                    "drop procedure if exists proc_array_test;;",
                    "create procedure proc_array_test(pstr_numbers text)",
                    "begin",
                    "    declare vstr_item varchar(255);",
                    "    declare vint_next int;",
                    "    set vint_next=1;",
                    "    while vint_next<=func_count_split(pstr_numbers,'+') do",
                    "        set vstr_item = func_indexOf(pstr_numbers,'+',vint_next);",
                    "        set vstr_item=REPLACE(vstr_item,':',' and phoneNumber=\'');",
                    "        set vstr_item=CONCAT('update t_cti_call_plan set state=1 where taskID=',vstr_item,'\';');",
                    "        set @vstr_sql=vstr_item;",
                    "        prepare stmt from @vstr_sql;",
                    "        execute stmt;",
                    "        deallocate prepare stmt;",
                    "       set vint_next=vint_next+1;",
                    "    end while;",
                    "end;;",
                    "DELIMITER ;",
                );
                $sqlstmt = "/**\r* 批量更新号码状态\r* @version 1.0 2014-11-1 by author\r* @example call proc_array_test"
                    . "('1212:13950076987+1211:13950076987+1210:13950076987');\r*/\r";
                break;
            case 'eg.create_trigger':
                $egsqlarr = array(
                    "delimiter ;;",
                    "create trigger trg_update_test after update on t_test for each row",
                    "begin",
                    "    call proc_update_test(t_test.id,t_test.status);",
                    "end;;",
                    "delimiter ;",
                    "--",
                    "DELIMITER ;;",
                    "create trigger trg_insert_user alter insert on t_user for each row",
                    "begin",
                    "    insert into t_finance_account (userID) values (t_user.id);",
                    "end;;",
                    "DELIMITER ;",
                );
                break;
            case 'eg.create_user':
                $egsqlarr = array(
                    '-- 创建用户同时授权',
                    "GRANT ALL PRIVILEGES ON databasename.tablename TO 'username'@'host' IDENTIFIED BY '123456';",
                    "GRANT ALL PRIVILEGES ON `db_test`.* TO 'db_user'@'localhost' IDENTIFIED BY '123456';",
                    "FLUSH PRIVILEGES;",
                    "-- 创建用户",
                    "CREATE USER 'username'@'host' IDENTIFIED BY 'password';",
                    "eg. CREATE USER 'db_user'@'127.0.0.1' IDENTIFIED BY '123456';",
                    "-- 授权用户",
                    "GRANT privileges ON databasename.tablename TO 'username'@'host'",
                    "eg. GRANT SELECT, INSERT ON `db_test`.* TO 'db_user'@'localhost';",
                    '-- 设置与更改用户密码',
                    "SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');",
                    '-- 撤销用户权限',
                    "REVOKE privilege ON databasename.tablename FROM 'username'@'host';",
                    "eg. REVOKE SELECT ON mq.* FROM 'dog2'@'localhost';",
                    '-- 删除用户',
                    "DROP USER 'username'@'host';",
                    "eg. DROP USER 'db_user'@'127.0.0.1';",
                    '-- 查看用户的授权',
                    "SHOW GRANTS FOR 'username'@'host';",
                    "eg. SHOW GRANTS FOR db_user@127.0.0.1;",
                );
                break;
            case 'eg.export_import':
                $egsqlarr = array(
                    '-- 导出整个数据库的表结构，包含数据',
                    'eg. mysqldump -uroot -proot --add-drop-table db_test > ./db_test.sql',
                    '-- 只导出整个数据库的表结构，不含数据（-d 等同于 --no-data）',
                    'eg. mysqldump -uroot -proot -d --add-drop-table db_test > ./db_test.sql',
                    '-- 只导出整个数据库数据，不包含表结构（-t 等同于 --no-create-info）',
                    'eg. mysqldump -uroot -proot -t db_test > ./db_test.sql',
                    '-- 导出整个数据库的表结构和存储过程、自定义函数，不含数据（-R 等同于 --routines）',
                    'eg. mysqldump -uroot -proot -d --add-drop-table -R db_test > ./db_test_routines.sql',
                    '-- 导出指定的数据表的表结构和数据',
                    'eg. mysqldump -uroot -proot db_test t_test1 t_test2 > ./db_test.test1n2.sql',
                    '-- 将 h1 服务器中的 db_test 数据库导入到 h2 中的 db_test 数据库中，h2 的 db_test 数据库必须存在否则'
                        . '会报错（-c参数表示压缩数据）',
                    'eg. mysqldump -h127.0.0.1 -uroot -proot -c db_test | mysql -h127.0.0.1 -uroot -proot db_test',
                    '-- 导入数据',
                    'eg. use db_test;',
                    'eg. source ./db_test.sql;',
                );
                break;
        }
        if ($egsqlarr) {
            $sqlstmt .= $isComment ? ('-- ' . implode("\r-- ", $egsqlarr)) : (implode(PHP_EOL, $egsqlarr));
        }
        return $sqlstmt;
    }

    /**
     * 获取数据操作示例语句
     */
    public static function getDbExampleSql($action, $db_name)
    {
        $sqlstmt = '';
        switch ($action) {
            case 'eg.dbvariables':        // 查看数据库变量信息
                // $sqlstmt = 'select VARIABLE_NAME, VARIABLE_VALUE FROM information_schema.global_variables;';
                $sqlstmt = 'show global variables;';
                break;
            case 'eg.dbstatus':
                $sqlstmt = 'show status;';
                break;
            case 'eg.dbcreate':        // 查看创建数据库命令
                $sqlstmt = 'show create database ' . $db_name . ';';
                break;
            case 'eg.dbschema':        // 查看创建数据库命令
                $sqlstmt = 'select TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, AUTO_INCREMENT, '
                    . 'CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT from information_schema.TABLES where '
                    . "information_schema.TABLES.TABLE_SCHEMA = '$db_name';";
                break;
        }
        return $sqlstmt;
    }

    /**
     * 获取数据表示例语句
     */
    public static function getTableExampleSql($action, $db_name, $table_name, $fields, $columns = [])
    {
        if (!$action) {
            return '';
        }
        $sqlstmt = '';
        switch ($action) {
            case 'eg.meta':
                $sqlstmt = 'SELECT `TABLE_CATALOG`,`TABLE_SCHEMA`,`TABLE_NAME`,`TABLE_TYPE`,`ENGINE`,`VERSION`,'
                    . '`ROW_FORMAT`,`TABLE_ROWS`,`AVG_ROW_LENGTH`,`DATA_LENGTH`,`MAX_DATA_LENGTH`,`INDEX_LENGTH`,'
                    . '`DATA_FREE`,`AUTO_INCREMENT`,`CREATE_TIME`,`UPDATE_TIME`,`CHECK_TIME`,`TABLE_COLLATION`,'
                    . '`CHECKSUM`,`CREATE_OPTIONS`,`TABLE_COMMENT` FROM `information_schema`.`TABLES` where '
                    . 'TABLE_SCHEMA=\'' . $db_name . '\'and TABLE_NAME=\'' . $table_name . '\';';
                break;
            case 'eg.select':
                if ($fields) {
                    $sqlstmt = 'select `' . implode('`, `', $fields) . '` from ' . $table_name . ' order by ' . $fields[0]
                        . ' desc limit 50;';
                }
                break;
            case 'eg.update':
                if ($fields) {
                    $sqlstmt = 'eg. update ' . $table_name . ' set `' . implode('`=?, `', $fields) . '`=? where '
                        . $fields[0] . '=?;';
                }
                break;
            case 'eg.insert':
                $sqlstmt = 'eg. insert into ' . $table_name . ' (`' . implode('`, `', $fields) . '`) values (:'
                    . implode(', :', str_replace('`', '', $fields)) . ');';
                break;
            case 'eg.delete':
                if ($fields) {
                    $sqlstmt = 'eg. delete from ' . $table_name . ' where ' . $fields[0] . '=?;';
                }
                break;
            case 'eg.desc': // desc命令
                $sqlstmt = "desc $table_name;";
                break;
            case 'eg.structure': // 查看表结构
                $sqlstmt = 'select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, '
                    . 'NUMERIC_SCALE, EXTRA, COLUMN_COMMENT from information_schema.columns WHERE TABLE_SCHEMA='
                    . "'$db_name' AND TABLE_NAME='$table_name';";
                break;
            case 'eg.index': // 查看索引
                $sqlstmt = "show index from $table_name;";
                break;
            case 'eg.triggers': // 查看触发器
                $sqlstmt = "show triggers like '$table_name';";
                break;
            case 'eg.alter': // alter命令
                $egsqlarr = array(
                    '-- 重命名表',
                    "eg. alter table `$table_name` rename to `$table_name`",
                    '-- 新增列',
                    "eg. alter table `$table_name` add `id` int comment 'id 列的备注信息';",
                    "eg. alter table `$table_name` add `colname1` varchar(255) comment 'colname1 列的备注信息';",
                    '-- 重命名列名',
                    "eg. alter table `$table_name` change colname1 colname2 varchar(255) comment '修改了 colname1 的列名为"
                        . "colname2 并增加备注信息';",
                    '-- 修改列的数据类型',
                    "eg. alter table `$table_name` modify colname2 varchar(32) comment 'colname2 的备注信息';",
                    "eg. alter table `$table_name` change colname2 colname2 varchar(64) not null;",
                    '-- 添加主键约束',
                    "eg. alter table `$table_name` add primary key(id);",
                    '-- 删除主键约束',
                    "eg. alter table `$table_name` drop primary key;",
                    '-- 添加外键约束',
                    "eg. alter table `$table_name` add foreign key (category_id) references t_category(id);",
                    '-- 解除外键约束',
                    "eg. alter table `$table_name` drop foreign key category_id",
                    '-- 删除外键约束（alter table 从表 add constraint 外键（FK_从表_主表）foreign key （从表外键字段） '
                        . 'references 主表（主键字段））',
                    "eg. alter table `$table_name` drop fk_test_category_id",
                    '-- 添加索引',
                    "eg. alter table `$table_name` add index idx_name1_name2 (colname1,colname2);",
                    '-- 删除索引',
                    "eg. alter table `$table_name` drop index idx_name1_name2;",
                    '-- 加唯一限制条件的索引',
                    "eg. alter table `$table_name` add unique idxu_colname2 (colname2);",
                );
                $sqlstmt = implode("\r", $egsqlarr);
                unset($egsqlarr);
                break;
            case 'eg.create': // 创建表
                $sqlstmt = "show create table $table_name;";
                break;
            case 'eg.duplicate': // 复制表
                $sqlstmt = 'eg. insert into table2 select * from table1;' . PHP_EOL;
                $sqlstmt .= 'eg. insert into table2 (field1,field2) select field1,field2 from table1;' . PHP_EOL;
                $sqlstmt .= 'eg. create table table2(select * from table1);' . PHP_EOL;
                $sqlstmt .= '-- -- mysql 不支持 select * into table2 from table 这种复制命令';
                break;
            case 'eg.truncate': // truncate命令
                $sqlstmt = "eg. truncate table $table_name;";
                break;
            case 'eg.count': // count命令
                $sqlstmt = "select count(*) from $table_name;";
                break;
            case 'eg.drop':
                $sqlstmt = "eg. drop table if exists $table_name;";
                break;
            case 'eg.testinsert':
                // COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE,
                // EXTRA, COLUMN_COMMENT
                $sqlstmt = 'eg. testdata ' . $table_name . ",10,1\r";
                $sqlstmt .= "// 第一行格式务必为：testdata {数据表名},{生成数量},{序号起始值}\r";
                $sqlstmt .= "// 字段定义每个一行，格式为：field=value\r";
                $sqlstmt .= "// 字段值最多可包含一个函数，支持的函数如下：\r";
                $sqlstmt .= "// 序号：{i}，随机数：{rand(min,max)}，范围值：{rang(A,B,C)}，时间戳：{time()}，时间："
                    . "{date('Y-m-d H:i:s')}\r";
                foreach ($columns as $column) {
                    if ($column['IS_NULLABLE'] == 'YES') {
                        continue; // 允许null的直接跳过
                    }
                    $field = $column['COLUMN_NAME'];
                    if ($column['DATA_TYPE'] == 'varchar' || $column['DATA_TYPE'] == 'char') {
                        $comment = ' // [' . $column['DATA_TYPE'] . '(' . $column['CHARACTER_MAXIMUM_LENGTH'] . ')] '
                            . $column['COLUMN_COMMENT'];
                    } else {
                        $comment = ' // [' . $column['DATA_TYPE'] . '] ' . $column['COLUMN_COMMENT'];
                    }
                    $comment = ' // [' . $column['DATA_TYPE'] . ($column['DATA_TYPE'] == 'varchar' ? '('
                        . $column['CHARACTER_MAXIMUM_LENGTH'] . ')' : '') . '] ' . $column['COLUMN_COMMENT'];
                    switch ($column['DATA_TYPE']) {
                        case 'int':
                        case 'integer':
                        case 'tinyint':
                        case 'smallint':
                        case 'bigint':
                        case 'decimal':
                        case 'float':
                        case 'real':
                        case 'double':
                            if ($field == 'id') {
                                $value = '{i}';
                            } else {
                                if (false !== strpos($field, 'time')) {
                                    $value = '{time()}';
                                } elseif (
                                    false !== strpos($field, 'status') ||
                                    false !== strpos($field, 'state') ||
                                    false !== strpos($field, 'sex')
                                ) {
                                    $value = '{rang(0,1,2)}';
                                } else {
                                    $value = '{rand(1,100)}';
                                }
                            }
                            break;
                        case 'datetime':
                        case 'date':
                        case 'time':
                        case 'timestamp':
                            $value = "{date('Y-m-d H:i:s')}";
                            break;
                        default:
                            if (false !== strpos($field, 'name') || false !== strpos($field, 'title')) {
                                $value = "'{$field}_{i}'";
                            } else {
                                $value = "'{$field}_{rand(1,100)}'";
                            }
                            break;
                    }
                    $sqlstmt .= $field . '=' . $value . $comment . "\r";
                }
                break;
        }
        return $sqlstmt;
    }

    /**
     * 获取视图示例语句
     */
    public static function getViewExampleSql($action, $db_name, $curview, $fields)
    {
        if (!$fields) {
            return false;
        }
        $sqlstmt = '';
        switch ($action) {
            case 'eg.select':
                $sqlstmt = 'select `' . implode('`, `', $fields) . '` from ' . $curview . ' order by ' . $fields[0]
                    . ' desc limit 50;';
                break;
            case 'eg.update':
                if ($fields) {
                    $sqlstmt = 'eg. update ' . $curview . ' set `' . implode('`=?, `', $fields) . '`=? where '
                        . $fields[0] . '=?;';
                }
                break;
            case 'eg.insert':
                $sqlstmt = 'eg. insert into ' . $curview . ' (`' . implode('`, `', $fields) . '`) values (:'
                    . implode(', :', str_replace('`', '', $fields)) . ');';
                break;
            case 'eg.delete':
                $sqlstmt = 'eg. delete from ' . $curview . ' where ' . $fields[0] . '=?;';
                break;
            case 'eg.structure':
                $sqlstmt = 'select COLUMN_NAME, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, '
                    . 'NUMERIC_SCALE, EXTRA, COLUMN_TYPE, COLUMN_KEY, COLUMN_COMMENT from information_schema.columns '
                    . "WHERE TABLE_SCHEMA='$db_name' AND TABLE_NAME='$curview';";
                break;
            case 'eg.alter':
                $sqlstmt = "eg. create or replace view $curview as ...;";
                break;
            case 'eg.create':
                $sqlstmt = 'show create view ' . $curview . ';';
                break;
        }
        return $sqlstmt;
    }

    /**
     * 获取程序示例语句
     */
    public static function getRoutineExampleSql($action, $curroutine, $curroutinetype, $routineParamNames)
    {
        $sqlstmt = '';
        switch ($action) {
            case 'eg.call':
                $sqlstmt = 'eg. ' . ($curroutinetype == 'function' ? 'select ' : 'call ') . $curroutine . '('
                    . ($routineParamNames ? implode(', ', $routineParamNames) : '') . ');';
                break;
            case 'eg.drop':
                $sqlstmt = 'eg. drop ' . $curroutinetype . ' if exists ' . $curroutine . ';';
                break;
            case 'eg.create':
                $sqlstmt = 'show create ' . $curroutinetype . ' ' . $curroutine . ';';
                break;
        }
        return $sqlstmt;
    }
}

?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MySQLArtisan 6.0.0</title>
    <!-- <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" rel="stylesheet"> -->
    <link href="install/static/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            font-size: 14px;
            background-color: #FFF;
        }

        aside {
            height: calc(100vh - 60px) !important;
            overflow-y: auto !important;
        }

        main {
            height: calc(100vh - 60px) !important;
            overflow-y: auto !important;
        }

        .main-table-container {
            width: 100%;
            max-height: 280px;
            overflow: auto
        }

        @media (min-width: 576px) {

            aside,
            main {
                height: 100% !important;
                overflow: visible !important;
            }

            .main-table-container {
                width: 100%;
                height: 100%
            }
        }

        .code-editor {
            background-color: #384548 !important;
            color: #abe338 !important;
        }

        .text-light-green {
            color: #abe338 !important
        }

        .tab-content {
            padding: 5px 0;
            background-color: #FFFFFF;
        }

        a {
            text-decoration: none;
        }

        .btn {
            white-space: nowrap !important;
        }

        .navbar {
            padding-top: .2rem;
            padding-bottom: .2rem;
        }

        .navbar-brand {
            font-size: 1.1rem;
        }

        .nav-tabs .nav-link {
            padding: 0.2rem .6rem;
        }

        .table-sm {
            font-size: 12px;
        }

        textarea.code-editor {
            min-height: 160px;
        }

        .list-group-item .operate-panel {
            right: 0;
            top: 9px;
            background-color: rgba(0, 0, 0, 0.5);
            padding: 0 5px;
            border-radius: 5px;
        }

        .list-group-item .operate-panel a {
            color: #FFF;
            padding: 0 5px;
        }

        .result-container {
            width: 100%;
            max-height: 550px;
            overflow-x: auto;
            background-color: #FFFFFF;
        }

        .result-container thead th {
            position: sticky;
            top: 5px;
            background-color: #FFFFFF;
            border-color: #dee2e6;
        }

        /* 骨架屏 */
        .skeleton {
            position: relative;
            overflow: hidden;
            pointer-events: none;
        }

        .skeleton:after {
            content: "";
            position: absolute;
            left: 0;
            top: 0;
            z-index: 9;
            width: 100%;
            height: 100%;
            background-color: #f8f9fa;
            display: block;
            border-radius: 2px;
        }
    </style>
</head>

<body>
    <div id="app">
        <header>
            <nav class="navbar navbar-expand-lg navbar-light bg-light">
                <div class="container-fluid">
                    <a class="navbar-brand" href="#">MySqlArtisan</a>
                    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                        <span class="navbar-toggler-icon"></span>
                    </button>
                    <div class="collapse navbar-collapse" id="navbarSupportedContent">
                        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                            <li class="nav-item">
                                <a class="nav-link active" aria-current="page" href="#">首页</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="?hi">新页面</a>
                            </li>
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                                    数据库
                                </a>
                                <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 Markdown</a></li>
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 Excel</a></li>
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 Html</a></li>
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 SQL</a></li>
                                    <li>
                                        <hr class="dropdown-divider">
                                    </li>
                                    <li><a class="dropdown-item" href="#">查看模型</a></li>
                                </ul>
                            </li>
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                                    数据表
                                </a>
                                <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 Markdown</a></li>
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 Excel</a></li>
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 Html</a></li>
                                    <li><a class="dropdown-item" href="#" @click.prevent="onShowExportOfDatabase()">导出结构到 SQL</a></li>
                                    <li>
                                        <hr class="dropdown-divider">
                                    </li>
                                    <li><a class="dropdown-item" href="#">查看模型</a></li>
                                </ul>
                            </li>
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                                    教程手册
                                </a>
                                <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
                                    <li v-for="(item,idx) of menuOfTutorial" :key="idx"><a class="dropdown-item" :href="item.link" target="_blank">{{item.text}}</a></li>
                                </ul>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="?action=view.phpinfo" target="_blank">phpinfo</a>
                            </li>
                        </ul>
                        <form class="d-flex" role="search">
                            <div class="input-group input-group-sm">
                                <select class="form-select" title="搜索引擎">
                                    <option value="">百度</option>
                                    <option value="">Bing</option>
                                    <option value="">谷歌</option>
                                    <option value="">有道词典</option>
                                    <option value="">百度词典</option>
                                    <option value="">Bing词典</option>
                                </select>
                                <input class="form-control" type="search" placeholder="输入关键词">
                                <button class="btn btn-secondary">搜索</button>
                            </div>
                        </form>
                    </div>
                </div>
            </nav>
        </header>
        <div class="container-fluid">
            <div class="row">
                <aside class="col-xxl-3 col-lg-4 col-md-5 col-sm-12">
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#schema-empty" type="button" role="tab">&equiv;</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#schema-list" type="button" role="tab">数据库</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#schema-search" type="button" role="tab">查找</button>
                        </li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane fade" id="schema-empty" role="tabpanel"></div>
                        <div class="tab-pane fade show active" id="schema-list" role="tabpanel">
                            <table class="table table-sm table-bordered table-striped table-hover">
                                <tr>
                                    <td>客户端IP</td>
                                    <td>{{hostInfo.clientIP}}</td>
                                </tr>
                                <tr>
                                    <td>服务器IP</td>
                                    <td>
                                        <span class="d-none">{{hostInfo.serverIP}}</span>
                                        <a href="#" class="btn-toggle-panel" data-target-el="prev" data-icon-type="2" title="点击显示或隐藏">&lowast;&lowast;&lowast;</a>
                                    </td>
                                </tr>
                                <tr>
                                    <td>服务器</td>
                                    <td>
                                        连接信息：
                                        <span class="d-none" title="127.0.0.1:3306">{{hostInfo.connectIP}}</span>
                                        <a href="#" class="btn-toggle-panel" data-target-el="prev" data-icon-type="2" title="点击显示或隐藏">&lowast;&lowast;&lowast;</a>
                                        <select class="form-select form-select-sm" title="点击切换" v-model="activeEnv">
                                            <option value="">==选择服务器==</option>
                                            <option :value="item.value" :title="item.text" v-for="(item,idx) of serverList" :key="idx">{{item.text}}</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr>
                                    <td>数据库</td>
                                    <td>
                                        <select class="form-select form-select-sm" title="点击切换" v-model="activeDatabase">
                                            <option value="">==选择数据库==</option>
                                            <option :value="item.value" :title="item.text" v-for="(item,idx) of databaseList" :key="idx">{{item.text}}</option>
                                        </select>
                                    </td>
                                </tr>
                                <tr>
                                    <td>数据库版本</td>
                                    <td>{{hostInfo.databaseVersion}}</td>
                                </tr>
                            </table>

                        </div>
                        <div class="tab-pane fade" id="schema-search" role="tabpanel">
                            <div class="input-group input-group-sm">
                                <span class="input-group-text">关键词</span>
                                <input type="text" class="form-control" />
                                <button class="btn btn-sm btn-secondary">查找</button>
                            </div>
                        </div>
                    </div>
                    <!-- /数据库列表 -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#objects-empty" type="button" role="tab">&equiv;</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#objects-table" type="button" role="tab">数据表</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#objects-view" type="button" role="tab">视图</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#objects-function" type="button" role="tab">函数</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#objects-procedure" type="button" role="tab">存储过程</button>
                        </li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane fade" id="objects-empty" role="tabpanel"></div>
                        <div class="tab-pane fade show active" id="objects-table" role="tabpanel">
                            <ul class="list-group list-group-flush list-group-numbered">
                                <li class="list-group-item d-flex">
                                    <span class="flex-grow-1">tbl_dlsdfjlsdfjlsdfjsldafdsafdafdafdsafsd</span>
                                    <div class="dropdown">
                                        <a class="dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
                                            &equiv;
                                        </a>
                                        <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
                                            <li><a class="dropdown-item" href="#">复制名称</a></li>
                                            <li><a class="dropdown-item" href="#">发送查询命令</a></li>
                                            <li><a class="dropdown-item" href="#">发送清空命令</a></li>
                                        </ul>
                                    </div>
                                </li>
                                <li class="list-group-item position-relative">tbl_dlsdfjlsdfjlsdfjsldafdsafdafdafdsafsd</li>
                                <li class="list-group-item position-relative">tbl_dlsdfjlsdfjlsdfjsldafdsafdafdafdsafsd</li>
                            </ul>
                            <nav class="mt-1">
                                <ul class="pagination pagination-sm">
                                    <li class="page-item"><a class="page-link" href="#">1</a></li>
                                    <li class="page-item"><a class="page-link" href="#">&lt;</a></li>
                                    <li class="page-item">
                                        <select class="form-select form-select-sm" title="跳转到指定页面">
                                            <option value="1">1</option>
                                            <option value="2">2</option>
                                            <option value="1000">1000</option>
                                        </select>
                                    </li>
                                    <li class="page-item"><a class="page-link" href="#">&gt;</a></li>
                                    <li class="page-item"><a class="page-link" href="#">1000</a></li>
                                </ul>
                            </nav>
                        </div>
                    </div>
                    <!-- /数据库对象列表 -->
                </aside>
                <!-- /aside -->
                <main class="col-xxl-9 col-lg-8 col-md-7 col-sm-12">
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="nav-item" role="presentation">
                            <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#database-inspector-empty" type="button" role="tab">&equiv;</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#database-inspector-info" type="button" role="tab">数据库信息</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#database-inspector-tables" type="button" role="tab">数据表</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#database-inspector-indexes" type="button" role="tab">索引</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#database-inspector-variables" type="button" role="tab">优化</button>
                        </li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane fade show active" id="database-inspector-empty" role="tabpanel"></div>
                        <div class="tab-pane fade" id="database-inspector-info" role="tabpanel">
                            <table class="table table-sm table-striped table-bordered table-hover table-click">
                                <tr>
                                    <td>Database name</td>
                                    <td>db_test</td>
                                </tr>
                                <tr>
                                    <td>Default charset</td>
                                    <td>utf8mb4</td>
                                </tr>
                                <tr>
                                    <td>Default collate</td>
                                    <td>utf8mb4_general_ci</td>
                                </tr>
                                <tr>
                                    <td>Database size</td>
                                    <td>16MB</td>
                                </tr>
                                <tr>
                                    <td>Table count</td>
                                    <td>20</td>
                                </tr>
                            </table>
                        </div>
                        <div class="tab-pane fade" id="database-inspector-tables" role="tabpanel">
                            <table class="table table-sm table-bordered table-striped table-hover table-click">
                                <thead>
                                    <tr>
                                        <th>NAME<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>ENGINE<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>ROWS<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>DATA_LENGTH<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>INDEX_LENGTH<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>AUTO_INCREMENT<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>CREATE_TIME<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>UPDATE_TIME<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>COLLACTION<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                        <th>COMMENT<a class="ps-1" href="#" title="点击隐藏此列">&times;</a></th>
                                    </tr>
                                </thead>
                            </table>
                        </div>
                        <div class="tab-pane fade" id="database-inspector-indexes" role="tabpanel">...</div>
                        <div class="tab-pane fade" id="database-inspector-variables" role="tabpanel">
                            <h6>数据库配置文件优化建议</h6>
                            <table class="table table-sm table-striped table-bordered table-hover table-click">
                                <tr>
                                    <th>变量名</th>
                                    <th>当前值</th>
                                    <th>建议值</th>
                                    <th>描述</th>
                                </tr>
                                <tr>
                                    <td>innodb_buffer_pool_size</td>
                                    <td>64 MB</td>
                                    <td>512 MB</td>
                                    <td>只需要用 Innodb 的话则可以设置它高达 70-80% 的可用内存。如果你的数据量不大，并且不会暴增，那么无需设置的太大了</td>
                                </tr>
                                <tr>
                                    <td>innodb_log_files_in_group</td>
                                    <td>2</td>
                                    <td>3</td>
                                    <td>循环方式将日志文件写到多个文件。推荐设置为 3</td>
                                </tr>
                                <tr>
                                    <td>innodb_thread_concurrency</td>
                                    <td>0</td>
                                    <td>32</td>
                                    <td>在并发量大的实例上，增加这个值，可以降低 InnoDB 在并发线程之间切换的花销，以增加系统的并发吞吐量</td>
                                </tr>
                                <tr>
                                    <td>key_buffer_size</td>
                                    <td>32 MB</td>
                                    <td>512 MB</td>
                                    <td>索引的缓冲区大小，对于内存在 4GB 左右的服务器来说，该参数可设置为 256 MB</td>
                                </tr>
                                <tr>
                                    <td>max_connections</td>
                                    <td>1000</td>
                                    <td>2000</td>
                                    <td>最大连接数量</td>
                                </tr>
                                <tr>
                                    <td>sort_buffer_size</td>
                                    <td>256 KB</td>
                                    <td>8 MB</td>
                                    <td>排序缓冲，增加 sort_buffer_size 值可以加速 ORDER BY 或 GROUP BY 操作，和连接数成正比，比如 1000 连接数就是
                                        8MB*1000=8GB 的内存使用量
                                    </td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <!-- /数据库信息 -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="nav-item" role="presentation">
                            <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#table-inspector-empty" type="button" role="tab">&equiv;</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#table-inspector-info" type="button" role="tab">数据表信息</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#table-inspector-columns" type="button" role="tab">数据列</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#table-inspector-indexes" type="button" role="tab">索引</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#table-inspector-ddl" type="button" role="tab">DDL</button>
                        </li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane fade show active" id="table-inspector-empty" role="tabpanel"></div>
                        <div class="tab-pane fade" id="table-inspector-info" role="tabpanel">
                            <table class="table table-sm table-striped table-bordered table-hover table-click">
                                <tr>
                                    <td>TABLE_NAME</td>
                                    <td>tbl_example</td>
                                </tr>
                                <tr>
                                    <td>TABLE_TYPE</td>
                                    <td>BASE TABLE</td>
                                </tr>
                                <tr>
                                    <td>ENGINE</td>
                                    <td>InnoDB</td>
                                </tr>
                                <tr>
                                    <td>Auto increment</td>
                                    <td>120</td>
                                </tr>
                                <tr>
                                    <td>Column count</td>
                                    <td>16</td>
                                </tr>
                                <tr>
                                    <td>Table rows</td>
                                    <td>120</td>
                                </tr>
                                <tr>
                                    <td>DATA_LENGTH</td>
                                    <td>16MB</td>
                                </tr>
                                <tr>
                                    <td>INDEX_LENGTH</td>
                                    <td>16MB</td>
                                </tr>
                                <tr>
                                    <td>Table size</td>
                                    <td>32MB</td>
                                </tr>
                                <tr>
                                    <td>CREATE_TIME</td>
                                    <td>2021-01-21 16:52:36</td>
                                </tr>
                                <tr>
                                    <td>UPDATE_TIME</td>
                                    <td>2021-01-21 16:53:30</td>
                                </tr>
                                <tr>
                                    <td>TABLE_COLLATION</td>
                                    <td>utf8_general_ci</td>
                                </tr>
                                <tr>
                                    <td>TABLE_COMMENT</td>
                                    <td>示例数据表</td>
                                </tr>
                            </table>
                        </div>
                        <div class="tab-pane fade" id="table-inspector-columns" role="tabpanel">...</div>
                        <div class="tab-pane fade" id="table-inspector-indexes" role="tabpanel">...</div>
                        <div class="tab-pane fade" id="table-inspector-ddl" role="tabpanel">
                            <textarea class="form-control form-control-sm bg-success text-warning" rows="12">CREATE TABLE `gam_category` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `pid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父级ID [cdl:json={"component":"select?table=gam_category&textfield=category_name&idfield=id","display":"create,edit"}]',
  `category_name` varchar(60) NOT NULL COMMENT '分类名称',
  `icon` varchar(200) NOT NULL DEFAULT '' COMMENT '图标',
  `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0=隐藏,1=显示)',
  `sort` smallint(5) unsigned NOT NULL DEFAULT '999' COMMENT '排序',
  `created_at` int(10) unsigned NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_category_name` (`category_name`),
  KEY `idx_pid` (`pid`),
  KEY `idx_status` (`status`),
  KEY `idx_sort` (`sort`)
) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='游戏分类表'
                        </textarea>
                        </div>
                    </div>
                    <!-- /数据表信息 -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#editor-empty" type="button" role="tab">&equiv;</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#editor-editor" type="button" role="tab">编辑器</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#editor-favorite" type="button" role="tab">常用脚本</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#editor-example" type="button" role="tab">示例脚本</button>
                        </li>
                        <li class="nav-item" role="presentation">
                            <button class="nav-link" data-bs-toggle="tab" data-bs-target="#editor-history" type="button" role="tab">历史记录</button>
                        </li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane fade" id="editor-empty" role="tabpanel">...
                        </div>
                        <div class="tab-pane fade show active" id="editor-editor" role="tabpanel">
                            <div class="h5ve-container card">
                                <div class="h5ve-toolbar card-header bg-gray d-flex" style="padding: 0;">
                                    <div class="flex-grow-1">
                                        <div class="btn-group" title="工具">
                                            <button class="btn btn-sm btn-run" data-cmd="run" title="复制" @click="onRunSql()">执行</button>
                                            <button class="btn btn-sm btn-run" data-cmd="run" title="复制" @click="onPreviewSql()">预览</button>
                                            <button class="btn btn-sm btn-save" data-cmd="save" title="保存" @click="onSaveSql()">保存</button>
                                            <button class="btn btn-sm btn-favorite" data-cmd="favorite" title="收藏SQL编辑器中的内容，注意，关闭浏览器后会自动清空收藏夹的数据">收藏</button>
                                        </div>
                                        <div class="btn-group" title="工具">
                                            <button class="btn btn-sm btn-select" data-cmd="select" title="全选">全选</button>
                                            <button class="btn btn-sm btn-copy" data-cmd="copy" title="复制">复制</button>
                                            <button class="btn btn-sm btn-paste" data-cmd="paste" title="粘贴">粘贴</button>
                                            <button class="btn btn-sm btn-delete" data-cmd="delete" title="删除选中的内容">删除</button>
                                            <button class="btn btn-sm btn-clear" data-cmd="clear" title="清空编辑器内容">清空</button>
                                            <button class="btn btn-sm btn-comment" data-cmd="comment" title="取消内容注释">取消注释</button>
                                        </div>
                                        <div class="btn-group" title="视图">
                                            <button class="btn btn-sm btn-print" data-cmd="print" title="打印编辑器内容">打印</button>
                                            <button class="btn btn-sm btn-fullwindow" data-cmd="fullwindow" title="浏览器窗口全屏">全屏</button>
                                            <button class="btn btn-sm btn-fullscreen" data-cmd="fullscreen" title="显示器满屏">满屏</button>
                                        </div>
                                        <div class="btn-group" title="帮助">
                                            <button class="btn btn-sm btn-help" data-cmd="help" title="帮助">帮助</button>
                                        </div>
                                    </div>
                                    <div>
                                        <button class="btn btn-sm btn-toggle-panel" data-target-el="#sqlfavorite_panel" title="查看收藏夹。只收藏最新 20 条数据，收藏夹内容会在关闭浏览器后自动删除。">收藏夹
                                            &equiv;</button>
                                    </div>
                                </div>
                                <div class="h5ve-content card-body d-flex" style="padding:0">
                                    <form class="flex-grow-1" method="post" action="?env=host_local&db=db_test&token=token_value">
                                        <textarea class="form-control form-control-sm code-editor align-self-stretch" title="当前支持{#object}变量,可以自动替换为当前表名或程序名; 点击3次自动去除注释" v-model="sqlContent">
                                        </textarea>
                                    </form>
                                    <div id="sqlfavorite_panel" class="d-none p-2 bg-light" title="收藏夹"
                                        style="width: 300px;">
                                        <ul></ul>
                                    </div>
                                </div>
                                <div class="h5ve-footer card-footer d-flex" style="padding:5px">
                                    <div class="flex-grow-1">自定义快速命令：</div>
                                    <div><span class="text-danger" id="sql_exectimer_panel" data-value="0.00">本次执行总耗时 0
                                            秒</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="tab-pane fade" id="editor-favorite" role="tabpanel">...</div>
                        <div class="tab-pane fade" id="editor-example" role="tabpanel">
                        <h6>基础操作</h6>
                                <div class="btn-group btn-group-sm me-1 mb-1">
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egselect"
                                        title="查询记录语句示例：SELECT 命令">查询数据</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egcount"
                                        title="查询表记录数量语句示例：COUNT 命令">统计行数</a>
                                    <div class="dropdown">
                                        <button class="btn btn-sm btn-secondary dropdown-toggle" type="button"
                                            id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
                                            增删改...
                                        </button>
                                        <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
                                            <li><a class="dropdown-item"
                                                    href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egupdate"
                                                    title="更新记录语句示例：UPDATE 命令">更新数据</a></li>
                                            <li><a class="dropdown-item"
                                                    href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=eginsert"
                                                    title="插入记录语句示例：INSERT 命令">插入数据</a></li>
                                            <li><a class="dropdown-item"
                                                    href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egdelete"
                                                    title="删除记录语句示例：DELETE 命令">删除数据</a></li>
                                        </ul>
                                    </div>
                                </div>
                                <h6>高级操作</h6>
                                <div class="btn-group btn-group-sm me-1 mb-1">
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egalter"
                                        title="更改表结构语句示例：ALTER 命令">修改表</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egcreate"
                                        title="创建表结构语句示例：CREATE 命令">创建表</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egduplicate"
                                        title="复制表语句示例：SELECT INTO 命令">复制表</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egtruncate"
                                        title="清空表记录语句示例：TRUNCATE 命令">重置表</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egdrop"
                                        title="丢弃表结构语句示例：DROP 命令">丢弃表</a>
                                </div>
                                <h6>元数据操作</h6>
                                <div class="btn-group btn-group-sm me-1 mb-1">
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egdesc"
                                        title="查看表结构语句示例：DESC 命令">表详情</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egstructure"
                                        title="查看表结构语句示例：STRUCTURE 命令">STRUCTURE</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egmeta"
                                        title="查询数据表元数据语句示例：META 命令">元数据信息</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egindex"
                                        title="查看表索引语句示例：INDEX 命令">索引信息</a>
                                    <a class="btn btn-sm btn-secondary"
                                        href="?env=host_local&db=db_test&object=tbl_example&objtype=table&token=token_value&action=egtriggers"
                                        title="查看表触发器语句示例：TRIGGERS 命令">触发器</a>
                                </div>
                        </div>
                        <div class="tab-pane fade" id="editor-history" role="tabpanel">...</div>
                    </div>
                    <!-- /SQL编辑器 -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li class="nav-item" role="presentation">
                            <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#result-empty" type="button" role="tab">&equiv;</button>
                        </li>
                        <li class="nav-item" role="presentation" v-for="(item,idx) of resultData" :key="idx">
                            <button class="nav-link" data-bs-toggle="tab" :data-bs-target="'#result-'+idx" type="button" role="tab">结果 {{idx+1}}</button>
                        </li>
                    </ul>
                    <div class="tab-content">
                        <div class="tab-pane fade show active" id="result-empty" role="tabpanel"></div>
                        <div class="tab-pane fade" :id="'result-'+idx" role="tabpanel" v-for="(item,idx) of resultData" :key="idx">
                            <div v-if="item.isQuery">
                                <table>
                                    <thead>
                                        <tr></tr>
                                    </thead>
                                </table>
                            </div>
                            <div v-else>22</div>
                        </div>
                    </div>
                    <!-- /结果面板 -->
                </main>
                <!-- /main -->
            </div>
        </div>
    </div>
    <!-- <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.29/vue.global.prod.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.25.0/axios.min.js"></script> -->
    <script src="install/static/js/bootstrap.bundle.min.js"></script>
    <script src="install/static/js/vue.global.prod.min.js"></script>
    <script src="install/static/js/axios.min.js"></script>
    <script>
        const AppComponent = {
            // data 配置
            data() {
                return {
                    // 教程网址收藏夹
                    menuOfTutorial: [{
                            text: 'MySQL 教程 | 菜鸟教程',
                            link: 'https://www.runoob.com/mysql/mysql-tutorial.html'
                        },
                        {
                            text: 'MySQL 教程_w3cschool',
                            link: 'https://www.w3cschool.cn/mysql/'
                        },
                        {
                            text: 'Mysql中时间和日期函数',
                            link: 'https://www.iteblog.com/mysql中时间和日期函数/'
                        },
                        {
                            text: 'MySQL：日期函数、时间函数总结',
                            link: 'https://www.cnblogs.com/ggjucheng/p/3352280.html'
                        },
                        {
                            text: 'mysql时间日期函数',
                            link: 'https://www.cnblogs.com/weibanggang/p/9574987.html'
                        },
                    ],
                    // 服务器信息
                    hostInfo:{
                        clientIP:'0.0.0.0',
                        serverIP:'',
                        connectionIP:'',
                        databaseVersion:'',
                        defaultServer:'',
                    },
                    // 可用的服务器列表
                    serverList:[
                        {text:"生产环境",value:"production"},
                        {text:"开发环境",value:"development"},
                    ],
                    // 数据库列表
                    databaseList:[
                        {text:"db_test",value:"db_test"},
                        {text:"db_example",value:"db_example"},
                    ],
                    // 数据表列表
                    dbTableList:[
                        {text:"tbl_test",value:"tbl_test"},
                    ],
                    // 视图表列表
                    dbViewList:[
                        {text:"tbl_test",value:"tbl_test"},
                    ],
                    // 函数列表
                    dbFunctionList:[
                        {text:"tbl_test",value:"tbl_test"},
                    ],
                    // 存储过程列表
                    dbProcedureList:[
                        {text:"tbl_test",value:"tbl_test"},
                    ],
                    // SQL 编辑器内容
                    activeEnv:'',
                    activeDatabase:'',
                    activeDbTable:'',
                    activeDbView:'',
                    activeDbFunction:'',
                    activeDbProcedure:'',
                    sqlContent:'',
                    // 执行结果
                    resultData:[],
                }
            },
            // methods 配置
            methods: {
                async initData(){
                    this.hostInfo=await this.stdResultData('?action=post.hostinfo');
                    this.serverList=this.hostInfo.serverList;
                    await this.onChangeServer();
                    this.activeEnv=this.hostInfo.defaultEnv;
                    this.activeDatabase=this.hostInfo.defaultDb;
                },
                initView(){
                // 移除骨架屏
                document.querySelectorAll('.skeleton').forEach(el => {
                    el.classList.remove("skeleton");
                })
                },
                async onRunSql() {
                    if(this.activeEnv==''){
                        alert('未选择服务器');
                        return;
                    }
                    if(this.activeDatabase==''){
                        alert('未选择数据库');
                        return;
                    }
                    if(this.sqlContent==''){
                        alert('未填写SQL内容');
                        return;
                    }
                    const msgInfo=await this.stdAxios('?action=post.run-sql',{
                        sql:this.sqlContent,
                        env:this.activeEnv,
                        database:this.activeDatabase,
                    })
                    if(msgInfo.status == '0'){
                        alert(msgInfo.message);
                        return;
                    }
                    this.resultData = msgInfo.data;
                },
                async onPreviewSql() {
                    const msgInfo=await this.stdAxios('?action=post.preview-sql',{sql:this.sqlContent})
                        console.log('result=',msgInfo);
                },
                // 切换服务器
                async onChangeServer(){
                    this.databaseList=await this.stdResultData('?action=post.databaselist',{env:this.activeEnv});
                },
                // 切换数据库
                onChangeDatabase(){

                },
                // 切换数据表
                onChangeDataTable(){

                },
                onShowExportOfDatabase() {

                },
                // 执行标准axios请求（返回 stdmessage 标准格式的数据）
                async stdAxios(url,data={},method='POST'){
                    const result=await axios({
                        method:method,
                        url:url,
                        data:data
                    })
                    return result.data;
                },
                // 执行标准结果请求
                async stdResultData(url,data={},method='POST'){
                    const result=await axios({
                        method:method,
                        url:url,
                        data:data
                    })
                    return result.data.data;
                }
            },
            // created 生命周期
            created(){
                this.initData();
            },
            // mounted 生命周期
            mounted() {
                this.initView();
            }
        }
        const app = Vue.createApp(AppComponent);
        app.mount("#app");
    </script>
</body>

</html>